#include<cerrno>
#include<cstring>
#include<pqxx/connection>
#include"query.h"
#include"postgrefs.h"

using namespace std;

using namespace pqxx;


/**
 * \brief Constructor - initializes the Config and the ConnectionPool
 * 
 */
PostgreFS::PostgreFS(const Config & config) : config(config), pool(config)
{
}

/**
 * \brief Calls the fusexx::fuse<PostgreFS>::main
 * 
 */
int PostgreFS::main()
{   return fusexx::fuse<PostgreFS>::main(config.args.argc, 
                                         config.args.argv, 
                                         NULL, 
                                         this);
}

/**
 * @param path path to the file
 * @param mode permissions and flags determining the type of the file
 *
 * 
 * \return on error : -ENAMETOOLONG (if path parameter is too long),-EMFILE (if pool.get_connection() fails) or 0 (on inode error); \n on sucess: returns inode number.
 */
int PostgreFS::mknod(const char *path, mode_t mode, dev_t rdev)
{   log::debug("PostgreFS::mknod(\"%s\", 0%o) called", path, mode);

    if (strlen(path) >= MAX_PATH_LENGTH)
    {   log::debug("PostgreFS::mknod(\"%s\", 0%o) exited with \"%s\"", path, mode, strerror(ENAMETOOLONG));
        return -ENAMETOOLONG;
    }

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::mknod(\"%s\", 0%o) exited with \"%s\"", path, mode, strerror(EMFILE));
        return -EMFILE;
    }

    char * delimiter = strrchr(path, '/');
    *delimiter = '\000';

    int inode = PostgreDB::query_mknod(conn, 
                                       delimiter+1, //name
                                       mode,
                                       rdev,
                                       PostgreDB::query_id(conn, path[0] ? path : "/") //parent
                                      );
    *delimiter = '/';
    log::debug("PostgreFS::mknod(\"%s\", %o) exited with \"%s\"=%d", path, mode, 
                                  inode<0 ? strerror(-inode) : "inode", inode);
    
    self->pool.put_connection(conn);
    return inode<0 ? inode : 0;
}


/**
 * Creates directory by calling PostgreFS::mknod with S_IFDIR 
 * 
 * @param path path to the file
 * @param mode permissions only
 * 
 * \return The return value of the call to  PostgreFS::mknod 
 */
int PostgreFS::mkdir(const char *path, mode_t mode)   
{   log::debug("PostgreFS::mkdir(\"%s\", 0%o) called - returning PostgreFS::mknod", path, mode);
    return mknod(path, S_IFDIR|mode, 0);
}

/**
 * Creates regular files using PostgreFS::mknod with S_IFREG and calls PostgreFS::open
 * 
 * @param path path to the file
 * @param mode permissions only
 * @param fi stores the file handle and the flags for open
 * 
 * \return on error: the error codes of PostgreFS::mknod and PostgreFS::open \n on sucess: the return value of PostgreFS::open 
 */
int PostgreFS::create(const char * path, mode_t mode, struct fuse_file_info *fi)
{   log::debug("PostgreFS::create(\"%s\", 0%o) called", path, mode);
    
    int exit_code;
    
    exit_code = mknod(path, S_IFREG|mode, 0);
    if (exit_code>=0)
        exit_code = open(path, fi);

    log::debug("PostgreFS::create(\"%s\", 0%o) exited with \"%s\"", path, mode, strerror(-exit_code));
    return exit_code;
}


/**
 * 
 * @param path path to the file
 * @param stbuf used to return the attributes
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path),-EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::getattr(const char *path, struct stat *stbuf) 
{   log::debug("PostgreFS::getattr(\"%s\") called", path);
    
    int ret, inode;

    memset(stbuf, 0, sizeof(struct stat)); //just for precaution

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::getattr(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }

    ret = PostgreDB::query_inode(conn, path);
    if(ret < 0) 
    {   log::debug("PostgreFS::getattr(\"%s\") exited with \"%s\"", path, strerror(-ret));                
        return ret;
    }
    
    inode = ret;

    int exit_code = PostgreDB::query_getattr(conn, inode, stbuf);
    log::debug("PostgreFS::getattr(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
    
    self->pool.put_connection(conn);
    return exit_code;
}


/**
 * Similar to PostgreFS::getattr for an opened file, the file handle is used to identify the file 
 * 
 * @param path path to the file
 * @param stbuf used to return the attributes
 * @param fi holds the file handle
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path), -EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{   log::debug("PostgreFS::fgetattr(\"%s\", fh=%d) called", path, fi->fh);
    
    int inode = fi->fh;

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::fgetattr(\"%s\", fh=%d) exited with \"%s\"", path, fi->fh, strerror(EMFILE));
        return -EMFILE;
    }
    
    int exit_code = PostgreDB::query_getattr(conn, inode, stbuf);
    log::debug("PostgreFS::fgetattr(\"%s\", fh=%d) exited with \"%s\"", path, fi->fh, strerror(-exit_code));
    
    self->pool.put_connection(conn);
    return exit_code;

}


/**
 * 
 * @param path path to the file
 * @param buf used to return the contents of the dir
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool), -ENOENT(no tree entry corresponding to path), -EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::readdir(const char *path,void *buf,fuse_fill_dir_t filler,
                       off_t offset, struct fuse_file_info *fi)
{   log::debug("PostgreFS::readdir(\"%s\") called", path);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::readdir(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }

    int inode = PostgreDB::query_inode(conn, path);
    if (inode<0)
    {   log::debug("PostgreFS::readdir(\"%s\") exited with \"%s\"", path, strerror(-inode));
        self->pool.put_connection(conn);
        return inode;
    }

    // fill standard entries
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);

    int exit_code = PostgreDB::query_readdir(conn, inode, buf, filler);
    log::debug("PostgreFS::readdir(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
    
    self->pool.put_connection(conn);
    return exit_code;
}

/**
 * Sets the atime and mtime attributes of a file
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path), -EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::utime(const char *path, struct utimbuf *time)
{   log::debug("PostgreFS::utime(\"%s\") called", path);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::utime(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }

    int inode = PostgreDB::query_inode(conn, path);
    if (inode<0)
    {   log::debug("PostgreFS::utime(\"%s\") exited with \"%s\"", path, strerror(-inode));
        self->pool.put_connection(conn);
        return inode;
    }

    int exit_code = PostgreDB::query_utime(conn, inode, time); 
    log::debug("PostgreFS::utime(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
    
    self->pool.put_connection(conn);
    return exit_code;
}


/**
 * 
 * @param path path to the file
 * @param mode permissions to be set
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path), -EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::chmod(const char *path, mode_t mode)
{   log::debug("PostgreFS::chmod(\"%s\", %o) called", path, mode);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::chmod(\"%s\", %o) exited with \"%s\"", path, mode, strerror(EMFILE));
        return -EMFILE;
    }

    int inode = PostgreDB::query_inode(conn, path);
    if (inode<0)
    {   log::debug("PostgreFS::chmod(\"%s\", %o) exited with \"%s\"", path, mode, strerror(-inode));
        self->pool.put_connection(conn);
        return inode;
    }

    int exit_code = PostgreDB::query_chmod(conn, inode, mode); 
    log::debug("PostgreFS::chmod(\"%s\", %o) exited with \"%s\"", path, mode, strerror(-exit_code));
    
    self->pool.put_connection(conn);
    return exit_code;
}

/**
 * 
 * @param path path to the file
 * @param uid user id
 * @param gid group id
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool), -EIO (on a query error),-ENOENT(no tree entry corresponding to path) \n on sucess: 0
 */
int PostgreFS::chown(const char *path, uid_t uid, gid_t gid)
{   log::debug("PostgreFS::chown(\"%s\", uid=%d, gid=%d) called", path, uid, gid);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::chown(\"%s\", uid=%d, gid=%d) exited with \"%s\"", path, uid, gid, strerror(EMFILE));
        return -EMFILE;
    }

    int inode = PostgreDB::query_inode(conn, path);
    if (inode<0)
    {   log::debug("PostgreFS::chown(\"%s\", uid=%d, gid=%d) exited with \"%s\"", path, uid, gid, strerror(-inode));
        self->pool.put_connection(conn);
        return inode;
    }

    int exit_code = PostgreDB::query_chown(conn, inode, uid, gid); 
    log::debug("PostgreFS::chown(\"%s\", uid=%d, gid=%d) exited with \"%s\"", path, uid, gid, strerror(-exit_code));
    
    self->pool.put_connection(conn);
    return exit_code;
}

/**
 * Opens a file and and saves the inode for further use by read and write. Increments the "inuse" counter in the database 
 *
 * @param path path to the file
 * @param fi the inode is saved in fi.fh (file handle)
 * 
 * \return on error: -EIO error, -ENOENT(no tree entry corresponding to path) \n on sucess: 0
 */
int PostgreFS::open(const char *path, struct fuse_file_info* fi)
{   log::debug("PostgreFS::open(\"%s\", fi->flags=0%o) called", path, fi->flags);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::open(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }

    int inode = PostgreDB::query_inode(conn, path);
    if (inode<0)
    {   log::debug("PostgreFS::open(\"%s\") exited with \"%s\"", path, strerror(-inode));
        self->pool.put_connection(conn);
        return inode;
    }

    // Save inode for furhter use by read and write.
    fi->fh = inode;
    
    log::debug("PostgreFS::open(): inode(\"%s\") = %d", path, inode);
    
    int exit_code = PostgreDB::query_inuse_inc(conn, inode, 1);
    log::debug("PostgreFS::open(\"%s\") exited with \"%s\"", path, strerror(-exit_code)); 

    self->pool.put_connection(conn);
    return exit_code;
}


/**
 * 
 * @param path path to the file
 * @param size this should be greater than the current size of the file or 0. Currently truncate to smaller size!=0 is
 * not supported. If this is the case -EIO is returned.
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path),-EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::truncate(const char *path, off_t size)
{   log::debug("PostgreFS::truncate(\"%s\", size=%d) called", path, size);
    
    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::truncate(\"%s\", size=%d) exited with \"%s\"", path, size, strerror(EMFILE));
        return -EMFILE;
    }

    int inode = PostgreDB::query_inode(conn, path);
    if (inode<0)
    {   log::debug("PostgreFS::truncate(\"%s\", size=%d) exited with \"%s\"", path, size, strerror(-inode));
        self->pool.put_connection(conn);
        return inode;
    }

    log::debug("PostgreFS::truncate(): inode(\"%s\") = %d", path, inode);
        
    int exit_code = PostgreDB::query_truncate(conn, inode, size);       
 
    log::debug("PostgreFS::truncate(\"%s\", size=%d) exited with \"%s\"", path, size, strerror(-exit_code)); 

    self->pool.put_connection(conn);
    return exit_code;    
}


/**
 * 
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path), -EIO (on query error) \n on sucess: 
 * number of bytes read
 */
int PostgreFS::read(const char* path, char* data, size_t size, off_t offset, struct fuse_file_info* fi)
{   log::debug("PostgreFS::read(\"%s\", size=%d, offset=%d) called", path, size, offset);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {  
        log::debug("PostgreFS::read(\"%s\", size=%d, offset=%d) exited with \"%s\"", path, size, offset, strerror(EMFILE));
        return -EMFILE;
    }

    int exit_code = PostgreDB::query_read(conn, fi->fh, data, size, offset);

    log::debug("PostgreFS::read(\"%s\", size=%d, offset=%d) exited with \"%s\"", path, size, offset, strerror(-exit_code));

    self->pool.put_connection(conn);
    return exit_code;     
}

/**
 * 
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to path), -EIO (on query error) \n on sucess: 0
 */
int PostgreFS::write(const char* path, const char* data, size_t size, off_t offset,struct fuse_file_info* fi)
{   log::debug("PostgreFS::write(\"%s\", size=%d, offset=%d) called", path, size, offset);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {  
        log::debug("PostgreFS::write(\"%s\", size=%d, offset=%d) exited with \"%s\"", path, size, offset, strerror(EMFILE));
        return -EMFILE;
    }

    int exit_code = PostgreDB::query_write(conn, fi->fh, data, size, offset);

    log::debug("PostgreFS::write(\"%s\", size=%d, offset=%d) exited with \"%s\"", path, size, offset, strerror(-exit_code));

    self->pool.put_connection(conn);
    return exit_code;     
}


/**
 * Decrements the "inuse" counter in the database and deletes the file if it has been marked for deleting and the file is no
 * longer in use.
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool), -ENOENT(no tree entry corresponding to path),-EIO (on a query error) \n on sucess: 0
 */
int PostgreFS::release(const char* path, struct fuse_file_info* fi)
{   log::debug("PostgreFS::release(\"%s\", fi->fh=%d, fi->flags=0%o) called", path, fi->fh, fi->flags);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::release(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }
         
    int exit_code = PostgreDB::query_inuse_inc(conn, fi->fh, -1);
    if(exit_code < 0)
    {   log::debug("PostgreFS::release(\"%s\") exited with \"%s\"", path, strerror(-exit_code)); 
        self->pool.put_connection(conn);
        return -exit_code;
    }

    exit_code = PostgreDB::query_purge_deleted(conn, fi->fh);
    log::debug("PostgreFS::release(\"%s\") exited with \"%s\"", path, strerror(-exit_code)); 

    self->pool.put_connection(conn);
    return exit_code;
}


/**
 * Deletes a regular file 
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to 
 * path),-EIO(on a query error) \n on sucess: 0
 */
int PostgreFS::unlink (const char *path) 
{   log::debug("PostgreFS::unlink(\"%s\") called", path);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::unlink(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }
  
    int inode, id;

    int exit_code = PostgreDB::query_inode_full(conn, path, &inode, &id);
    if(exit_code < 0)
    {   log::debug("PostgreFS::unlink(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
        self->pool.put_connection(conn);
        return -exit_code;
    }

    exit_code = PostgreDB::query_rm_tree_entry(conn, id);
    if(exit_code < 0)
    {   log::debug("PostgreFS::unlink(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
        self->pool.put_connection(conn);
        return -exit_code;
    }

    exit_code = PostgreDB::query_purge_deleted(conn, inode);
    log::debug("PostgreFS::unlink(\"%s\") exited with \"%s\"", path, strerror(-exit_code));

    self->pool.put_connection(conn);
    return exit_code;
}


/**
 * Deletes a directory 
 * 
 * \return on error: -EMFILE (if there are no available connections in the pool),-ENOENT(no tree entry corresponding to 
 * path),-EIO(on a query error) \n on sucess: 0
 */
int PostgreFS::rmdir (const char *path) 
{   log::debug("PostgreFS::rmdir(\"%s\") called", path);

    connection* conn;
    if ((conn=self->pool.get_connection()) == NULL)
    {   log::debug("PostgreFS::rmdir(\"%s\") exited with \"%s\"", path, strerror(EMFILE));
        return -EMFILE;
    }
  
    int inode, id;

    int exit_code = PostgreDB::query_inode_full(conn, path, &inode, &id);
    if(exit_code < 0)
    {   log::debug("PostgreFS::rmdir(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
        self->pool.put_connection(conn);
        return -exit_code;
    }

    exit_code = PostgreDB::query_rm_tree_entry(conn, id);
    if(exit_code < 0)
    {  log::debug("PostgreFS::rmdir(\"%s\") exited with \"%s\"", path, strerror(-exit_code));
       self->pool.put_connection(conn);
       return -exit_code;
    }

    exit_code = PostgreDB::query_purge_deleted(conn, inode);
    log::debug("PostgreFS::rmdir(\"%s\") exited with \"%s\"", path, strerror(-exit_code));

    self->pool.put_connection(conn);
    return exit_code;
}
